package org.noote.spidy.core;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;

import org.noote.libs.bluetooth.SPPClient;
import org.noote.libs.camera.CameraInterface;
import org.noote.libs.camera.broadcast.CameraBroadcast;
import org.noote.libs.camera.helper.Helper_Camera;
import org.noote.libs.camera.listener.CameraPreviewListener;
import org.noote.libs.graphicbuffer.FPSCounter;
import org.noote.libs.graphicbuffer.GraphicBuffer;
import org.noote.libs.robot.Robot_ServoMotor;
import org.noote.libs.spidy.anim.Spidy_WildRobot;
import org.noote.libs.spidy.comm.BluetoothCommandEvents;
import org.noote.libs.spidy.comm.BluetoothCommandProtocol;
import org.noote.libs.spidy.comm.IPServerCommandEvents;
import org.noote.libs.spidy.comm.IPServerCommandProtocol;
import org.noote.libs.spidy.configuration.RobotSettings;
import org.noote.libs.spidy.configuration.RobotStates;
import org.noote.libs.tcpip.IPEvent_Server;
import org.noote.libs.tcpip.IPServer;
import org.noote.libs.tcpip.helper.Helper_WiFi;

@SuppressLint("HandlerLeak")
public class SpidyCore_Activity extends Activity {
	private static final String LOG_TAG = "SpidyCore_Activity";

	private String _sCurrentIPAddress = "0.0.0.0";

	public static final String PREFS_NAME = "Spidy_Core";
	public static final String PREFS_NAME_CORE_NAME = "Core_Name";
	public static final String PREFS_NAME_BTADDRESS = "BT_Address";
	public static final String PREFS_NAME_SERVER_PORT = "Server_Port";
	public static final String PREFS_NAME_CAMERA_PREVIEW_PORT = "CameraPreview_Port";

	private static final int REQUEST_ENABLE_BT = 1;
	private static final int REFRESH_SETTINGS = 2;
	
	private static final int STEP_READY_BLUETOOTH_STARTED = 0x01;
	private static final int STEP_READY_BLUETOOTH_VERSION = 0x02;
	private static final int STEP_READY_BLUETOOTH_POSITION = 0x04;

	private String _sCoreName = "NooTe's Robot";
	private String _sCoreVersion = "0.2";
	private int _iServerPort = 8086;
	private int _iCameraServerPort = 8088;
	private String _sBTAddressDevice = "";

	private SPPClient _bluetoothClient = null; // bluetooth client
	private SpidyIPServer _monitoringServer = new SpidyIPServer(); // IP server
	
	private CameraInterface _camera = new CameraInterface();
	private CameraBroadcast _cameraBroadcast = null;
	
	protected final Lock _lockOnTick = new ReentrantLock();
	protected final Lock _lockOnFrame = new ReentrantLock();
	
	private VisualMessage_View _visualMessage = null;

	private RobotSettings _settings = new RobotSettings();
	private RobotStates _states = new RobotStates();
	protected int _iReadyStep = 0;
	private boolean _bTransmitData = false;
	
	int _iAnimationChoosen=0;

	private Spidy_WildRobot _bot = new Spidy_WildRobot();
	private FPSCounter _botFrameCounter = new FPSCounter();

	private PowerManager.WakeLock wl = null;

	protected static final int MESSAGE_UI_LOG = 0;
	protected static final int MESSAGE_UI_TOAST = 1;
	protected static final int MESSAGE_UI_VISUAL = 2;
	
	private void loadSettings() {
		SharedPreferences settings = getSharedPreferences(SpidyCore_Activity.PREFS_NAME, 0);
		
		_iServerPort = Integer.valueOf(settings.getString(SpidyCore_Activity.PREFS_NAME_SERVER_PORT, String.valueOf(_iServerPort)));
		_iCameraServerPort = Integer.valueOf(settings.getString(SpidyCore_Activity.PREFS_NAME_CAMERA_PREVIEW_PORT, String.valueOf(_iCameraServerPort)));
		_sCoreName = settings.getString(SpidyCore_Activity.PREFS_NAME_CORE_NAME, _sCoreName);
		_sBTAddressDevice = settings.getString(SpidyCore_Activity.PREFS_NAME_BTADDRESS, _sBTAddressDevice);

		boolean bNeedReSaving = false;
		if (_iServerPort < 1 || _iServerPort > 65535)
		{
			_iServerPort = 8085;
			bNeedReSaving = true;
		}
		if (_iCameraServerPort < 1 || _iCameraServerPort > 65535)
		{
			_iCameraServerPort = 8088;
			bNeedReSaving = true;
		}
		if (_sCoreName.length() == 0)
		{
			_sCoreName = "NooTe's Robot";
			bNeedReSaving = true;
		}

		if (bNeedReSaving)
		{
			SharedPreferences.Editor editor = settings.edit();
			editor.putString(SpidyCore_Activity.PREFS_NAME_SERVER_PORT,	String.valueOf(_iServerPort));
			editor.putString(SpidyCore_Activity.PREFS_NAME_CAMERA_PREVIEW_PORT,	String.valueOf(_iCameraServerPort));
			editor.putString(SpidyCore_Activity.PREFS_NAME_CORE_NAME, _sCoreName);
			editor.putString(SpidyCore_Activity.PREFS_NAME_BTADDRESS, _sBTAddressDevice);

			editor.commit();
		}
	}

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		getCurrentIPAddress();
		loadSettings();

		TextView textViewServer = (TextView) findViewById(R.id.textViewServer);
		textViewServer.setText(_sCurrentIPAddress + ":" + _iServerPort);

		TextView textViewBroadcast = (TextView) findViewById(R.id.textViewBroadcast);
		textViewBroadcast.setText(_sCurrentIPAddress + ":" + _iCameraServerPort);

		TextView textViewLog = (TextView) findViewById(R.id.textViewLog);
		textViewLog.setMovementMethod(new ScrollingMovementMethod());
		
		_visualMessage = new VisualMessage_View(this);
		FrameLayout layoutVisualMessage = (FrameLayout) findViewById(R.id.layoutVisualMessage);		
		layoutVisualMessage.addView(_visualMessage);
		layoutVisualMessage.setOnClickListener(new OnClickListener()
        {
			@Override
			public void onClick(View v) {
				// start BT comm here !
				if(_bluetoothClient != null)
				{
					if(_bluetoothClient.isConnected())
					{
						btDisconnect();
					}
					else
					{
						btConnect();
					}
				}
			}
        });
		
        // bot creation
		_bot.create();
        
		// Bluetooth
		_bluetoothClient = new SPPClient(_bluetoothEvents);
		
		addLog("Launch monitoring server on " + _sCurrentIPAddress + ":" + _iServerPort + "...");		
		if(!_monitoringServer.create(_iServerPort))
		{
			Log.d(LOG_TAG, "Error then start BC server :(");
			showToast("Error then start monitoring server :(");
		}

		if (Helper_Camera.checkCameraHardware(getApplicationContext()))
		{
			/** create camera */
			if(!_camera.open(Helper_Camera.getBackCamera()))
			{
				showToast("Cannot start back camera !");
				return;
			}
			_camera.setPreviewListener( new CameraPreviewListener()
				{
					@Override
					public void onStartPreview()
					{
						addLog("Camera preview started");
					}
	
					@Override
					public void onGrabbedFrame(GraphicBuffer frame)
					{
						if (_lockOnFrame.tryLock()) {
							try {
								// TODO :)
								// A copy to a thread for process image !								
							} finally {
								_lockOnFrame.unlock();
							}
						}						
					}
	
					@Override
					public void onStopPreview()
					{
						addLog("Camera preview stopped");
					}
				});		
			_cameraBroadcast = new CameraBroadcast(this, _camera);
			_cameraBroadcast.setOnEventListener(new CameraBroadcast.onEventListener()
				{
					@Override
					public void onConnected(String sIPClient)
					{
						addLog("BC - Get a connection from " + sIPClient);
					}

					@Override
					public void onDisconnected(String sIPClient)
					{
						addLog("BC - Close connection from " + sIPClient);
					}

					@Override
					public void onError(String sMessage)
					{
						addLog("BC - Server error : " + sMessage);
					}

				});

			// Launch camera broadcast server
			if (!_cameraBroadcast.isConnected())
			{
				Log.d(LOG_TAG, "Launch broadcast on " + _sCurrentIPAddress	+ ":" + _iCameraServerPort + "...");
				addLog("Launch camera broadcast ip server on " + _sCurrentIPAddress + ":" + _iCameraServerPort + "...");
				if (!_cameraBroadcast.createServer(_iCameraServerPort))
				{
					Log.d(LOG_TAG, "Error then start BC server :(");
					showToast("Error then start BC server :(");
				}
			}
		}
		else
		{
			showToast("Sorry, your device have no default camera !");
		}
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data)
	{
		switch (requestCode)
		{
		case REQUEST_ENABLE_BT:
			if (resultCode == RESULT_OK)
			{
				btHaveAddress();
			}
			break;
        case REFRESH_SETTINGS:
        	if (resultCode == RESULT_OK)
        	{
        		loadSettings();
        		// TODO more, relaunch IP server or BT client...
        	}
            break;			
		}
	}

	private final BroadcastReceiver _BluetoothReceiver = new BroadcastReceiver()
	{
		@Override
		public void onReceive(Context context, Intent intent)
		{
			String action = intent.getAction();

			// When disconnected bluetooth device
			if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action))
			{
				_states._bBluetoothOnline = false;
				addLog("BT - ACL disconnected...");
			}
		}
	};

	@Override
	public void onStart()
	{
		super.onStart();
		
		// Register for broadcasts when a device is disconnected !
		IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED);
		this.registerReceiver(_BluetoothReceiver, filter);

		final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
		if (mBluetoothAdapter != null && !mBluetoothAdapter.isEnabled())
		{
			Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
			startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
			return;
		}
	}

	@Override
	public void onStop()
	{
		this.unregisterReceiver(_BluetoothReceiver);

		super.onStop();
	}

	@Override
	protected void onPause()
	{
		super.onPause();

		if (wl != null)
			wl.release();
		
        if(_camera != null)
        	_camera.stopPreview();
	}

	@Override
	protected void onResume()
	{
		super.onResume();

		if (wl == null)
		{
			PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
			wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "Spidy Core");
		}
		if (wl != null)
			wl.acquire();
		
        if(_camera != null)
        	_camera.startPreview();
	}

	@Override
	public void onBackPressed() {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setTitle(getString(R.string.dlg_exit_title));
		builder.setMessage(getString(R.string.dlg_exit_text));
		builder.setPositiveButton(getString(R.string.dlg_button_exit),
				new DialogInterface.OnClickListener()
				{
					@Override
					public void onClick(DialogInterface dialog, int id)
					{
						btDisconnect();
						finish();
					}
				});
		builder.setNegativeButton(getString(R.string.dlg_button_cancel),
				new DialogInterface.OnClickListener()
				{
					@Override
					public void onClick(DialogInterface dialog, int id)
					{
						dialog.cancel();
					}
				});
		AlertDialog alert = builder.create();
		alert.show();
	}

	@Override
    public boolean onCreateOptionsMenu(Menu menu)
	{
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.main, menu);
        return true;
    }
    
    /* Handles item selections */
    @Override
	public boolean onOptionsItemSelected(MenuItem item)
    {
        switch (item.getItemId())
        {
        case R.id.itemSettings:
            LaunchSetting();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
    
	private void LaunchSetting()
	{
		Intent intent = new Intent(this, Setting_Activity.class);
		startActivityForResult(intent, REFRESH_SETTINGS);
	}

	private boolean btHaveAddress()
	{
		SharedPreferences settings = getSharedPreferences( SpidyCore_Activity.PREFS_NAME, 0);
		_sBTAddressDevice = settings.getString( SpidyCore_Activity.PREFS_NAME_BTADDRESS, "");
		if (_sBTAddressDevice.length() == 0)
		{
			LaunchSetting();
			return false;
		}

		return true;
	}

	private void isReady()
	{
		if (_states._bReady == false && _iReadyStep == (STEP_READY_BLUETOOTH_STARTED | STEP_READY_BLUETOOTH_VERSION | STEP_READY_BLUETOOTH_POSITION))
		{
			if(BluetoothCommandProtocol.getProtocolVersion().equalsIgnoreCase(_settings._sRobot_Version))
			{
				_bot.setAnimation(0); // stand by !
				
				_states._bReady = true;
				_bTransmitData = true;
				addLog("Ready !");
				
				showVisualMessage(VisualMessage_View.MESSAGE_VISUAL_OK);
			}
  			else
			{
				// Bad protocol version !
				showToast("Bad protocol version :(");
				addLog("Bad protocol version :(");
				addLog("Requiered version "+BluetoothCommandProtocol.getProtocolVersion()+", robot version is "+_settings._sRobot_Version);
				
				showVisualMessage(VisualMessage_View.MESSAGE_VISUAL_ERROR);
				
				btDisconnect();
			}
		}
		else if (_states._bReady == false)
		{
			String sWait = "Wait ";
			sWait += ((_iReadyStep & STEP_READY_BLUETOOTH_STARTED) == STEP_READY_BLUETOOTH_STARTED) ? "*" : ".";
			sWait += ((_iReadyStep & STEP_READY_BLUETOOTH_VERSION) == STEP_READY_BLUETOOTH_VERSION) ? "*" : ".";
			sWait += ((_iReadyStep & STEP_READY_BLUETOOTH_POSITION) == STEP_READY_BLUETOOTH_POSITION) ? "*" : ".";
			addLog(sWait);
			
			showVisualMessage(VisualMessage_View.MESSAGE_VISUAL_WAIT_CONNECT);
		}
	}

	void btConnect()
	{
		addLog("Try BT connection...");
		showVisualMessage(VisualMessage_View.MESSAGE_VISUAL_WAIT_CONNECT);
		
		if (!btHaveAddress()) return;

		if (!_bluetoothClient.isConnected())
		{
			if (!_bluetoothClient.connect(_sBTAddressDevice))
			{
				showToast("Connection to BT:" + _sBTAddressDevice + " impossible !!!");
				showVisualMessage(VisualMessage_View.MESSAGE_VISUAL_ERROR);
			}
		}
	}

	void btDisconnect()
	{
		_states._bReady = false;
		_bTransmitData = false;

		if(_bluetoothClient != null)
		{
			addLog("Try BT disconnection...");
			_bluetoothClient.sendMessage(BluetoothCommandProtocol.sendQuit()); // quit
			if (_bluetoothClient.isConnected())
			{
				if (!_bluetoothClient.disconnect())
				{
					showToast("Disconnection from BT:" + _sBTAddressDevice	+ " impossible !!!");
				}
			}
		}
	}

	final BluetoothCommandEvents _bluetoothEvents = new BluetoothCommandEvents()
	{
		@Override
		public void onConnected()
		{
			_states._bBluetoothOnline = true;
			addLog("BT - Online...");
			_bluetoothClient.sendMessage(BluetoothCommandProtocol.sendAskConnection()); // ask connect
		}

		@Override
		public void onDisconnected()
		{
			_states._bBluetoothOnline = false;
			_states._bReady = false;
			addLog("BT - disconnect...");
		}

		@Override
		public void onError(String sError)
		{
			showToast("BT - Get Error : " + sError);
		}

		@Override
		public void onText(String sText)
		{
			showToast("BT - Get text : " + sText);
		}

		@Override
		public void onReady(String sStatus)
		{
			if (sStatus.equalsIgnoreCase("ok") || (sStatus.equalsIgnoreCase("ping") && (_iReadyStep & STEP_READY_BLUETOOTH_STARTED) != STEP_READY_BLUETOOTH_STARTED))
			{
				_iReadyStep |= STEP_READY_BLUETOOTH_STARTED;

				_bluetoothClient.sendMessage(BluetoothCommandProtocol.sendAskVersion()); // ask version

				isReady();
			}
			else if (sStatus.equalsIgnoreCase("ping"))
			{
				_bluetoothClient.sendMessage(BluetoothCommandProtocol.sendAlive()); // Yes, i'm alive :)
			}
		}

		@Override
		public void onVersion(String robotname, String robotversion)
		{
			_iReadyStep |= STEP_READY_BLUETOOTH_VERSION;

			_settings._sRobot_Name = robotname;
			_settings._sRobot_Version = robotversion;
			
			_bluetoothClient.sendMessage(BluetoothCommandProtocol.sendAskAllServoPositions()); // ask version

			isReady();
		}

		@Override
		public void onTick(final float fTickTime)
		{
			if (_states._bReady && _bTransmitData)
			{
				if (_lockOnTick.tryLock()) {
					try {
						_botFrameCounter.tick();
						
			    		if(_bot.isAnimationFinish())
			    			_bot.setAnimation(_iAnimationChoosen);
			    		_bot.doAnimation(_botFrameCounter.getTimeSlice());
			        	_bot.update();				
						
						_bluetoothClient.sendMessage(_bot.buildTransmitMessage());
					} finally {
						_lockOnTick.unlock();
					}
				}
			}
		}
		@Override
		public void onRange(final int iFrontRange) {
			if(_states._bReady && _bTransmitData)
			{
				_states._iFrontRange = iFrontRange;
			}
		}

		@Override
		public void onServoPositions(SparseArray<Float> positions)
		{
			for(Robot_ServoMotor servo : _bot.getServoList())
			{
				Float fPos = positions.get(servo.getID());
				if(fPos != null)
				{
					servo.setRealPosition(fPos.floatValue());
				}
			}
			
			if(_states._bReady == false && (_iReadyStep&STEP_READY_BLUETOOTH_POSITION)!=STEP_READY_BLUETOOTH_POSITION)
			{
				_iReadyStep |= STEP_READY_BLUETOOTH_POSITION;
				
				isReady();
			}
		}
	};

	private void getCurrentIPAddress()
	{
		_sCurrentIPAddress = Helper_WiFi.getCurrentIPAddress_WiFi_Only(this,
				true);
		if (_sCurrentIPAddress == "")
		{
			new AlertDialog.Builder(this)
					.setTitle("Not WiFi active connexion detected...")
					.setPositiveButton("Ok",
							new DialogInterface.OnClickListener()
							{
								@Override
								public void onClick(DialogInterface dialog, int whichButton)
								{
									finish();
								}
							}).show();
			return;
		}
	}

	class SpidyIPServer extends IPServer
	{
		@Override
		public IPEvent_Server createServerEvents()
		{
			return new IPServerCommandEvents()
			{
				@Override
				public void onConnected()
				{
					addLog("IP Connected");
				}

				@Override
				public void onDisconnected()
				{
					_states._bNetworkOnline = false;
					addLog("IP disconnected");
				}

				@Override
				public void onError(String sError) {
					addLog("IP - Get error : " + sError);
				}

				@Override
				public void onText(String sText) {
					addLog("IP - Get text : " + sText);
				}

				@Override
				public void onHello(String sVersion, String sControllerName)
				{
					sendMessage(IPServerCommandProtocol.sendHello(_sCoreName, _sCoreVersion));
				}

				@Override
				public void onAskVersion()
				{
					sendMessage(IPServerCommandProtocol.sendVersion(_sCoreName,	_sCoreVersion));
				}

				@Override
				public void onAskSettingCameraBroadcast()
				{
					sendMessage(IPServerCommandProtocol.sendSettingsCameraBroadcast(_sCurrentIPAddress, _iCameraServerPort));
				}

				@Override
				public void onReset()
				{
					// _bluetoothClient.sendMessage(BluetoothCommandProtocol.sendReset());
				}

				@Override
				public void onAskVibrate(long[] datas)
				{
					sendVibration(datas);
				}
			};
		};
	}

	public void sendVibration(long[] vibrate)
	{
		// Create our Notification Manager:
		NotificationManager gNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		if (gNotificationManager != null) {
			// Create our Notification that will do the vibration:
			Notification gNotification = new Notification();
			// Set the vibration:
			gNotification.vibrate = vibrate;
			// send vibration :)
			gNotificationManager.notify(1, gNotification);
		}
	}

	public void addLog(String sMsg)
	{
		_UI_Handler.sendMessage(_UI_Handler.obtainMessage(MESSAGE_UI_LOG, sMsg));
	}

	public void showToast(String sMsg)
	{
		addLog(sMsg);
		_UI_Handler.sendMessage(_UI_Handler.obtainMessage(MESSAGE_UI_TOAST, sMsg));
	}

	public void showVisualMessage(int iMessage)
	{
		_UI_Handler.sendMessage(_UI_Handler.obtainMessage(MESSAGE_UI_VISUAL, iMessage));
	}

	final Handler _UI_Handler = new Handler()
	{
		@Override
		public void handleMessage(Message msg)
		{
			switch (msg.what)
			{
			case MESSAGE_UI_LOG:
				{
					TextView textViewLog = (TextView) findViewById(R.id.textViewLog);
					textViewLog.setText(textViewLog.getText() + (String) msg.obj + "\n");
				}
				break;

			case MESSAGE_UI_TOAST:
				{
					Toast.makeText(getApplicationContext(), (String) msg.obj, Toast.LENGTH_LONG).show();
				}
				break;

			case MESSAGE_UI_VISUAL:
			{
				if(_visualMessage != null)
				{
					_visualMessage.enableMessage((Integer)msg.obj);
				}
			}
			break;
			}
		}
	};
}
